package com.zhangkunhao.notes.dao;

import com.zhangkunhao.notes.util.MyDataSource;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;

import java.util.List;
import java.util.Random;

/**
 * 增删改和填充占位符接口的实现类
 *
 * @author 张坤浩
 */
public class BaseDao implements Dao {

    private BaseDao() {
    }

    private static final BaseDao BASE_DAO = new BaseDao();

    public static BaseDao getInstance() {
        return BASE_DAO;
    }

    /**
     * 适用任何表的通用增删改方法
     */
    @Override
    public int update(String sql, Object... args) {

        Connection conn = null;
        PreparedStatement ps = null;
        int row = 0;
        try {
            //1.获取连接
            conn = MyDataSource.getInstance().getConnection();
            //2.获取PreparedStatement,用于执行sql
            ps = conn.prepareStatement(sql);
            //3.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            //4.执行sql
            row = ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //5.释放资源
            MyDataSource.getInstance().free(conn, ps, null);
        }
        return row;
    }


    /**
     * 批量发布功德活动，不通用
     *
     * @param sql
     * @param batchNumber
     * @param args
     * @return
     */
    public static int batchUpdate(String sql, Integer batchNumber, Object... args) {
        Connection conn = null;
        PreparedStatement ps = null;
        //用于随机获取指定范围内的各个活动的报名限制人数和功德量
        Random random = new Random();
        //影响行数
        int row = 0;
        try {
            //1.获取连接
            conn = MyDataSource.getInstance().getConnection();
            //2.获取PreparedStatement,用于执行sql
            ps = conn.prepareStatement(sql);
            //3.批量填充占位符
            for (int i = 1; i <= batchNumber; i++) {
                //注意sql的索引从1开始
                ps.setObject(1, args[0]);
                //得到报名限制人数和功德量的随机数（大于等于1）
                ps.setInt(2, random.nextInt((Integer) args[1]) + 1);
                ps.setString(3, (String) args[2] + i);
                ps.setInt(4, random.nextInt((Integer) args[3]) + 1);

                //1.积攒sql语句(装车)
                ps.addBatch();

                if (i % batchNumber == 0) {
                    //2.批量执行sql（运货）
                    int[] ints = ps.executeBatch();
                    row = ints.length;
                    //3.清空sql语句（卸车）
                    ps.clearBatch();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //5.释放资源
            MyDataSource.getInstance().free(conn, ps, null);
        }
        return row;
    }

    @Override
    /**
     * 通用的查询多条数据并封装到集合
     *
     * @param sql
     * @param clazz
     * @param args
     * @param <T>
     * @return
     */
    public <T> List<T> queryList(String sql, Class<T> clazz, Object... args) {

        List<T> list = new ArrayList<>();
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // 1.获取连接
            conn = MyDataSource.getInstance().getConnection();

            // 2.获取PreparedStatement,用于执行sql
            ps = conn.prepareStatement(sql);

            // 3.填充占位符
            fillParameter(ps, args);

            // 4.执行sql,得到结果集(查询得到的数据表)
            rs = ps.executeQuery();

            // 5.获取结果集的元数据
            ResultSetMetaData metaData = rs.getMetaData();

            // 6.获取结果集的列数
            int columnCount = metaData.getColumnCount();

            // 7.获取结果集中的数据
            while (rs.next()) {
                T t = clazz.newInstance();
                // 8.获取结果集列名
                for (int i = 0; i < columnCount; i++) {
                    String columnName = metaData.getColumnLabel(i + 1);
                    // 9.获取结果名字列值
                    Object columValue = rs.getObject(columnName);
                    // 10.利用反射为反射设置值,注意：结果集的列名（别名也行）必须与属性名一致
                    Field field = clazz.getDeclaredField(columnName);
                    // 用暴力反射，忽略访问权限
                    field.setAccessible(true);
                    field.set(t, columValue);
                }
                list.add(t);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭连接
            MyDataSource.getInstance().free(conn, ps, rs);
        }
        return list;
    }

    @Override
    /**
     * 通用的查询一条数据并封装为对象
     * 需要修改的地方：
     * 一：返回值
     * 1.返回值的类型不确定，用泛型
     * 2.返回值的对象不确定，用反射
     * 二；结果集
     * 1.结果集有几列不确定
     * 2.列的类型不确定
     * 解决: ResultSetMetaData(结果集的元数据)
     * getColumnCount() : 取结果集的列数
     * getColumnName() : 取结果集的列名
     * getColumnLabel() : 取结集列的别名，没有别名按getColumnName()取
     */
    public <T> T query(String sql, Class<T> clazz, Object... args) {
        //比如get(sql,Student.class,0,1)

        T t = null;
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            // 1.获取连接
            conn = MyDataSource.getInstance().getConnection();

            // 2.获取PreparedStatement,用于执行sql
            pstmt = conn.prepareStatement(sql);

            // 3.填充占位符
            fillParameter(pstmt, args);

            // 4.执行sql,得到结果集(查询得到的数据表)
            rs = pstmt.executeQuery();

            // 5.获取结果集的元数据
            ResultSetMetaData metaData = rs.getMetaData();

            // 6.获取结果集的列数
            int columnCount = metaData.getColumnCount();

            // 7.获取结果集中的数据
            if (rs.next()) {
                t = clazz.newInstance();
                // 8.获取结果集列名
                for (int i = 0; i < columnCount; i++) {
                    String columnName = metaData.getColumnLabel(i + 1);
                    // 9.获取结果名字列值
                    Object columValue = rs.getObject(columnName);
                    // 10.利用反射为反射设置值,注意：结果集的列名（别名）必须与属性名一致
                    Field field = clazz.getDeclaredField(columnName);
                    // 用暴力反射，忽略访问权限
                    field.setAccessible(true);
                    field.set(t, columValue);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            MyDataSource.getInstance().free(conn, pstmt, rs);
        }
        return t;
    }


    /**
     * 填充占位符的方法
     *
     * @param pstmt
     * @param args
     * @throws SQLException
     */
    @Override
    public void fillParameter(PreparedStatement pstmt, Object[] args) throws SQLException {
        for (int i = 0; i < args.length; i++) {
            pstmt.setObject(i + 1, args[i]);
        }
    }
}
